package com.taxigang.server;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

import com.allen_sauer.gwt.log.client.Log;
import com.google.appengine.api.channel.ChannelMessage;
import com.google.appengine.api.channel.ChannelService;
import com.google.appengine.api.channel.ChannelServiceFactory;
import com.taxigang.server.TaxiGang.Status;
import com.taxigang.shared.ChitInfo;
import com.taxigang.shared.LoginInfo;
import com.taxigang.shared.TaxiGangInfo;
import com.taxigang.shared.TaxiGanguserInfo;
import com.taxigang.shared.TaxiGangInfo.LeaveResult;

public class TaxiGangDAO {
	// ExceptionHandler exceptionHandler = new ExceptionHandler();
	//	EntityManager em = EMF.get().createEntityManager();
	//
	//	public EntityManager getEntityManager() {
	//		return em;
	//	}
	private EntityManager em;
	public TaxiGangDAO(EntityManager em){
		this.em = em;
	}
	synchronized public boolean add(TaxiGang user) {
		try {
			em.getTransaction().begin();
			em.persist(user);
			em.getTransaction().commit();
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		} finally {
			// em.close();
			return false;
		}
	}

	synchronized public TaxiGangInfo add(TaxiGangUserDAO taxiGangUserDAO,TaxiGanguserInfo  leaderInfo,TaxiGangInfo taxiganginfo, ChitInfo message,
			LoginInfo loginInfo) {

		//code to create a page entry - use locally and create entry in online version by hand
		//		double lat = 47.7666667;
		//		double lng = 11.5666667;
		//		
		//		double lat = -26.8694439;        //indulkana
		//		double lng = 133.3793185;
		//		String pageId = "124122537716969";
		//		
		//		long geobox = GeoBox.getInstance().getIndex(lat, lng);
		//		Page page = new Page("Taxigang-Indulkana",geobox, pageId,"access_token");
		//		try {
		//			em.getTransaction().begin();
		//			em.persist(page);
		//		} catch (Exception ex) {
		//			Log.error(ex.getMessage());
		//		} finally {
		//			em.getTransaction().commit();
		//		}

		String[] pageDetails = getPage(taxiganginfo);

		TaxiGang taxiGang = null;
		TaxiGangInfo info = null;
		TaxiGanguser leader=null;
		try {
			//create a taxigang and persist it
			em.getTransaction().begin();
			taxiganginfo.setLocale(loginInfo.getLocale());
			taxiGang = new TaxiGang(taxiganginfo, message, pageDetails);
			//taxiGang.addLeader(leader);

			em.persist(taxiGang);
			em.getTransaction().commit();

			info = taxiGang.getInfo();

			//add the leader to my taxigangs
			em.getTransaction().begin();
			leader = taxiGangUserDAO.get(leaderInfo.getId());
			leader.addTaxigang(taxiGang.getInfo().getId());
			//leader.updateAddressHistory(taxiganginfo);
			em.persist(leader);
			em.getTransaction().commit();

			em.getTransaction().begin();
			taxiGang = em.find(TaxiGang.class, info.getId());

			taxiGang.addLeader(leader);
			em.persist(taxiGang);
			em.getTransaction().commit();


			info = taxiGangUserDAO.getMembers(info,leader.getId());

			//leader.addTaxigang(info.getId());


		} catch (Exception ex) {
			Log.error(ex.getMessage());
		} 

		info.setLink("http://apps.facebook.com/taxigang/?tg="+info.getId()+"&locale="+loginInfo.getLocale());

		Mailer.getInstance().sendMessage(
				"A taxigang was created by " + leader.getDisplayname(), info,
				loginInfo);


		return info;
	}

	private String[] getPage(TaxiGangInfo taxiganginfo) {
		double startLat = taxiganginfo.getStartLat();
		double startLng = taxiganginfo.getStartLng();

		double destLat = taxiganginfo.getDestinationLat();
		double destLng = taxiganginfo.getDestinationLng();


		Query query = em.createNamedQuery("Page.findAll");
		Collection<Page> resultList = query.getResultList();
		for (Page page : resultList){
			boolean isStartNearPage = GeoBox.getInstance().isNearby(page.getGeobox(),startLat,startLng);
			if (!isStartNearPage){
				boolean isDestNearPage = GeoBox.getInstance().isNearby(page.getGeobox(),destLat,destLng);
				if (isDestNearPage){
					return page.getPageId();
				}
			}else{
				return page.getPageId();
			}
		}

		//expect "174406889328525"
		String[]noDetails = {null,null};
		return noDetails;
	}

	public void addMessage(TaxiGangInfo taxigangInfo, ChitInfo chitInfo) {
		TaxiGang taxiGang = null;
		try {
			em.getTransaction().begin();
			taxiGang = em.find(TaxiGang.class, taxigangInfo.getId());
			taxiGang.addMessage(chitInfo);

			em.persist(taxiGang);
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		} finally {
			em.getTransaction().commit();
		}
	}

	// * Next add the code to TaxiGangController to remove a record

	// synchronized public boolean remove(TaxiGang user) {
	// return remove(user.getKeyId());
	// }

	synchronized public boolean remove(TaxiGangInfo taxiGangInfo) {

		try {
			em.getTransaction().begin();
			TaxiGang taxiGang = em.find(TaxiGang.class, taxiGangInfo.getId());
			taxiGang.prepareRemoval();
			em.remove(taxiGang);
			em.getTransaction().commit();
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		} finally {
			// em.close();
			return false;
		}
	}

	// * Finally add the code to TaxiGangController to update a record

	synchronized public boolean update(TaxiGang taxigang) {
		try {
			em.getTransaction().begin();
			TaxiGang taxigangx = em.find(TaxiGang.class, taxigang.getKeyId());
			taxigangx.setStatus(taxigang.getStatus());

			// other fields
			em.getTransaction().commit();
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		} finally {
			// em.close();
			return false;
		}
	}

	public TaxiGangInfo get(TaxiGangUserDAO taxiGangUserDAO,long id) {
		TaxiGang taxiGang = em.find(TaxiGang.class, id);
		TaxiGangInfo info = taxiGang.getInfo();
		info = taxiGangUserDAO.getMembers(info,-1);
		return info;
	}

	synchronized public TaxiGangInfo[] getTaxiGangByUser(TaxiGanguser taxiGangUser) {

		Date now = new Date();
		List<TaxiGangInfo>result = new ArrayList<TaxiGangInfo>();

		List<Long>myTaxiGangIds =  taxiGangUser.getMyTaxiGangs();
		int s = myTaxiGangIds.size();
		for (Long taxiGangId : myTaxiGangIds) {
			TaxiGang taxiGang = em.find(TaxiGang.class, taxiGangId);
			if (taxiGang != null){
				if (now.after(taxiGang.getDepartureTime())) {
					em.getTransaction().begin();
					taxiGang.setStatus(Status.CLOSED);
					
					//if may be closed but if not yet awarded return it too
					if (!taxiGang.getAwardingUsers().contains(taxiGangUser.getId())){
						
						//when only one awarding user then do not return (leader is alone in taxigang)
						if (taxiGang.getMembers().size() >1){
							TaxiGangInfo info = taxiGang.getInfo();
							result.add(info);
						}
					}
					em.getTransaction().commit();
					
					
				} else {
					result.add(taxiGang.getInfo());
				}
			}
		}


		TaxiGangInfo[] arrayTaxiGangInfo = result.toArray(
				new TaxiGangInfo[0]);
		Arrays.sort(arrayTaxiGangInfo);
		return arrayTaxiGangInfo;
	}

	synchronized public Collection<TaxiGang> getAll() {

		Query query = em.createNamedQuery("TaxiGang.findAll");
		return query.getResultList();
	}

	synchronized public TaxiGangInfo[] getTaxiGangsByProximity(
			TaxiGangInfo taxiganginfo, Date now) {

		long[][] geoboxs = GeoBox.getInstance().getBox(
				taxiganginfo.getStartLat(), taxiganginfo.getStartLng());
		Query query = em.createNamedQuery("TaxiGang.findByProximity");
		query.setParameter("geobox00", geoboxs[0][0]);
		query.setParameter("geobox01", geoboxs[0][1]);
		query.setParameter("geobox02", geoboxs[0][2]);
		query.setParameter("geobox10", geoboxs[1][0]);
		query.setParameter("geobox11", geoboxs[1][1]);
		query.setParameter("geobox12", geoboxs[1][2]);
		query.setParameter("geobox20", geoboxs[2][0]);
		query.setParameter("geobox21", geoboxs[2][1]);
		query.setParameter("geobox22", geoboxs[2][2]);

		List<TaxiGangInfo> taxiganginfoList = new ArrayList<TaxiGangInfo>();
		TaxiGangInfo[] arrayTaxiGangInfo = null;
		EntityTransaction transaction = em.getTransaction();
		try {
			Collection<TaxiGang> taxigangs = query.getResultList();
			Log.debug("TaxiGangDAO:getTaxiGangsByProximity found "
					+ taxigangs.size() + " suggestions");

			for (TaxiGang taxiGang : taxigangs) {
				if (now.after(taxiGang.getDepartureTime())) {
					transaction.begin();
					taxiGang.setStatus(Status.CLOSED);
					transaction.commit();
				} else {
					TaxiGangInfo info = taxiGang.getInfo();
					info.setAffinity(getAffinity(taxiganginfo, info));
					taxiganginfoList.add(info);
				}
			}
			arrayTaxiGangInfo = taxiganginfoList.toArray(new TaxiGangInfo[0]);
			Arrays.sort(arrayTaxiGangInfo);
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		return arrayTaxiGangInfo;

	}

	private double getAffinity(TaxiGangInfo taxiganginfo1,
			TaxiGangInfo taxiganginfo2) {

		double affinity = Math.abs(taxiganginfo1.getStartLat()
				- taxiganginfo2.getStartLat());
		affinity += Math.abs(taxiganginfo1.getStartLng()
				- taxiganginfo2.getStartLng());
		affinity += Math.abs(taxiganginfo1.getDestinationLat()
				- taxiganginfo2.getDestinationLat());
		affinity += Math.abs(taxiganginfo1.getDestinationLng()
				- taxiganginfo2.getDestinationLng());
		return affinity;
	}

	// public boolean closeOldTaxiGang(TaxiGangInfo taxigangInfo, Date now){
	// TaxiGang taxiGang = em.find(TaxiGang.class,taxigangInfo.getId());
	// boolean closed = false;
	// if (now.after(taxiGang.getDepartureTime())){
	// taxiGang.setStatus(Status.CLOSED);
	// em.flush();
	// closed = true;
	// }
	// transaction.commit();
	// return closed;
	// }

	synchronized public void deleteAll() {
		EntityTransaction transaction = em.getTransaction();
		try {
			Query query = em.createNamedQuery("TaxiGang.findAll");

			Collection<TaxiGang> taxigangs = query.getResultList();
			for (TaxiGang taxiGang : taxigangs) {
				transaction.begin();
				em.remove(taxiGang);
				transaction.commit();
			}
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		}
	}

	synchronized public void setMember(TaxiGangInfo taxigangInfo,
			TaxiGanguser user) {
		EntityTransaction transaction = em.getTransaction();
		TaxiGang taxiGang = em.find(TaxiGang.class, taxigangInfo.getId());
		try {
			transaction.begin();

			taxiGang.addMember(user.getId());

			//			if (taxigangInfo.getMemberName(0) == null) {
			//				taxiGang.setMember(0, taxiGanguserInfo);
			//			} else if (taxigangInfo.getMemberName(1) == null) {
			//				taxiGang.setMember(1, taxiGanguserInfo);
			//			} else if (taxigangInfo.getMemberName(2) == null) {
			//				taxiGang.setMember(2, taxiGanguserInfo);
			//			} else {
			//				// throw new TaxiGangFullException("gang is full");
			//			}
			em.persist(taxiGang);
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		} finally {
			em.getTransaction().commit();
		}

	}

	synchronized public void inform(TaxiGangInfo taxigangInfo, Inform inform,
			LoginInfo loginInfo) {
		TaxiGang taxiGang = em.find(TaxiGang.class, taxigangInfo.getId());

		// now inform others
		ChannelService channelService = ChannelServiceFactory
		.getChannelService();
		for (Long memberId : taxiGang.getMembers()) {
			String channelKey = "" + memberId;
			Log.info("TaxiGangDAO:informing:" + memberId);
			try {
				String message = inform.name() + "\r" + loginInfo.getId()
				+ "\r" + loginInfo.getEmailAddress();
				channelService.sendMessage(new ChannelMessage(channelKey,
						inform.name() + ":" + loginInfo.getId()));
			} catch (Exception ex) {
				Log.error("inform:" + ex.getMessage() + ":channelKey="
						+ channelKey);
			}

		}

	}

	synchronized public LeaveResult leave(TaxiGangInfo taxiganginfo,
			TaxiGanguser taxiGangUser) {

		LeaveResult leaveResult = LeaveResult.OK;

		TaxiGang taxigang = em.find(TaxiGang.class, taxiganginfo.getId());

		List<Long> members = taxigang.getMembers();
		if (!members.contains(taxiGangUser.getId())){
			return LeaveResult.CANT_LEAVE;
		}
		
		Long leaderId = members.get(0);
		Long leaveeId = taxiGangUser.getId();
		if (members.size() > 0 && leaderId==leaveeId){
			//leader with other members
			int i=0;
			for (Long userId : members){
				if (i++ >= 1){
					if (userId != taxiGangUser.getId()){
						return LeaveResult.CANT_LEAVE;
					}
				}
			}

		} 

		em.getTransaction().begin();
		taxigang.removeMember(taxiGangUser);
		if (taxigang.getMembers().contains(taxiGangUser.getId())){
			leaveResult = LeaveResult.LEFT_BUT_STILL_MEMBER;
		}


		//		if (taxigang.getMemberId(0) != null
		//				&& taxigang.getMemberId(0).equals(taxiganguserInfo.getId())) {
		//			taxigang.setMemberId(0, null);
		//			taxigang.setMemberName(0, null);
		//		} else if (taxigang.getMemberId(1) != null
		//				&& taxigang.getMemberId(1).equals(taxiganguserInfo.getId())) {
		//			taxigang.setMemberId(1, null);
		//			taxigang.setMemberName(1, null);
		//		} else if (taxigang.getMemberId(2) != null
		//				&& taxigang.getMemberId(2).equals(taxiganguserInfo.getId())) {
		//			taxigang.setMemberId(2, null);
		//			taxigang.setMemberName(2, null);
		//		} else if (taxigang.getLeaderId() != null
		//				&& taxigang.getLeaderId().equals(taxiganguserInfo.getId())) {
		//			if (taxigang.getMemberId(0) != null
		//					|| taxigang.getMemberId(1) != null
		//					|| taxigang.getMemberId(2) != null) {
		//				return LeaveResult.CANT_LEAVE;
		//			}
		//			taxigang.setLeaderId(null);
		//			taxigang.setLeaderName(null);
		//		} else {
		//			throw new RuntimeException("user is not member");
		//		}
		//update(taxigang);
		em.persist(taxigang);
		em.getTransaction().commit();

		return leaveResult;
	}

	public TaxiGangInfo getTaxiGang(String invitation) {
		Long invite = Long.parseLong(invitation);
		TaxiGang taxigang = em.find(TaxiGang.class, invite);

		TaxiGangInfo taxigangInfo = null;
		if (taxigang != null){
			taxigangInfo = taxigang.getInfo();
			if (!Status.CLOSED.equals(taxigangInfo.getStatus())){
				Date now = new Date();
				if (now.after(taxigang.getDepartureTime())) {
					em.getTransaction().begin();
					taxigang.setStatus(Status.CLOSED);
					taxigangInfo.setStatus(Status.CLOSED.toString());
					em.getTransaction().commit();
				} 
			}
		}
		return taxigangInfo;
	}
	
	/**
	 * Add the awarding user to list of awarding users
	 * @param taxiganginfo
	 * @param userId
	 */
	public void updateAwardingUser(TaxiGangInfo taxiganginfo, TaxiGanguser taxiGanguser) {
		TaxiGang taxigang = em.find(TaxiGang.class, taxiganginfo.getId());
		em.getTransaction().begin();
		taxigang.addAwardingUser(taxiGanguser.getId());
		em.getTransaction().commit();
	}
	public void expire(TaxiGangInfo taxiGangInfo) {
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.DAY_OF_MONTH,-1);
		TaxiGang taxigang = em.find(TaxiGang.class, taxiGangInfo.getId());
		em.getTransaction().begin();
		taxigang.setDepartureTime(calendar.getTime());
		em.getTransaction().commit();
		
	}

	public void resetPermanents() {
		EntityTransaction transaction = em.getTransaction();
		try {
			Query query = em.createNamedQuery("TaxiGang.findAll");
			
			Collection<TaxiGang> taxigangs = query.getResultList();
			for (TaxiGang taxigang : taxigangs) {
				if (taxigang.getPermanent()){
					transaction.begin();
					Date depDate = taxigang.getDepartureTime();
					Calendar cal = Calendar.getInstance();
					cal.setTime(depDate);
					cal.add(Calendar.DAY_OF_YEAR, 1);
					taxigang.setDepartureTime(cal.getTime());
					transaction.commit();
			
				}
			}
		} catch (Exception ex) {
			Log.error(ex.getMessage());
		}
	}

}
